﻿using System;
using RaytracerLib.MathLib;
using YAXLib;
using RaytracerLib.Rays;
using System.Diagnostics;

namespace RaytracerLib.Cameras
{
    public abstract class Camera : OrientedElement
    {
        [YAXNode("Active")]
        private bool m_active;

        protected Film m_film;
        private string m_film_name;

        public Camera(Vector3 a_right, Vector3 a_up) :
            base(a_right, a_up)
        {
            Name = "Camera";
            Film = new Film();
        }

        [YAXNode("Film")]
        private string FilmName
        {
            get
            {
                if (m_film != null)
                    return m_film.Name;
                else
                    return m_film_name;
            }
            set
            {
                m_film_name = value;
            }
        }

        [YAXOnDeserialized]
        private void OnDeserialized(Scene a_scene)
        {
            Film = a_scene.Films[FilmName];
            FilmName = null;
        }

        public Film Film
        {
            get
            {
                return m_film;
            }
            set
            {
                if (m_film == value)
                    return;

                if ((value != null) && (value.Scene != Scene))
                    value.Scene = Scene;

                m_film = value;
            }
        }

        public override bool Remove()
        {
            return Scene.Cameras.Remove(this);
        }

        public bool Active
        {
            get
            {
                return m_active;
            }
            set
            {
                if (m_active == value)
                    return;

                m_active = value;

                if (m_active)
                    if (Scene != null)
                        Scene.ActiveCamera = this;
            }
        }

        public override Scene Scene
        {
            get
            {
                return base.Scene;
            }
            internal set
            {
                if (Scene == value)
                    return;

                if (m_active)
                {
                    if (Scene != null)
                        Scene.ActiveCamera = null;
                }

                base.Scene = value;

                if (value == null)
                    m_film = null;
                else
                {
                    if (m_film != null)
                        m_film.Scene = value;
                }
            }
        }

        public void SetLookAt(Vector3 a_point)
        {
            Forward = a_point - Pos;
            var up = Vector3.CrossProduct(Forward, Right);
            Right = Vector3.CrossProduct(up, Forward);
            Up = Vector3.CrossProduct(Forward, Right);
        }

        public override string Name
        {
            get
            {
                return base.Name;
            }
            set
            {
                if (Name == value)
                    return;

                if (Scene != null)
                    if (Scene.Cameras[value] != null)
                        throw new InvalidOperationException();

                base.Name = value;
            }
        }

        internal Vector2 UVToPixel(double a_x, double a_y)
        {
            return UVToPixel(new Vector2(a_x, a_y));
        }

        internal Vector2 PixelToUV(double a_x, double a_y)
        {
            return PixelToUV(new Vector2(a_x, a_y));
        }

        /// <summary>
        /// </summary>
        /// <param name="a_x">from 0 to RenderOptions.Width exlusive</param>
        /// <param name="a_y">from 0 to RenderOptions.Height exlusive</param>
        /// <returns></returns>
        internal abstract CameraRay CreateRay(Vector2 a_uv);

        internal abstract Vector2 UVToPixel(Vector2 a_uv);
        internal abstract Vector2 PixelToUV(Vector2 a_pixel);
    }
}